/*
 * Copyright (c) 2024 Shenzhen Kaihong Digital Industry Development Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { BaseButtonlessDfuImpl } from './BaseButtonlessDfuImpl'
import { DfuBaseService } from './DfuBaseService'
import { SecureDfuError } from './error/SecureDfuError'
import ble from '@ohos.bluetooth.ble';
import hilog from '@ohos.hilog';

export abstract class ButtonlessDfuImpl extends BaseButtonlessDfuImpl {
  private static DFU_STATUS_SUCCESS: number = 1;

  private static OP_CODE_ENTER_BOOTLOADER_KEY: number = 0x01;
  private static OP_CODE_RESPONSE_CODE_KEY: number = 0x20;
  private static OP_CODE_ENTER_BOOTLOADER: Uint8Array =
    new Uint8Array([ButtonlessDfuImpl.OP_CODE_ENTER_BOOTLOADER_KEY]);

  constructor(service: DfuBaseService) {
    super(service);
    this.TAG = 'ButtonlessDfuImpl'
  }

  protected abstract getResponseType(): number;
  protected abstract getButtonlessDfuCharacteristic(): ble.BLECharacteristic;
  public abstract shouldScanForBootloader(): boolean;

  private getStatusCode(response: Uint8Array, request: number): number {
    if (response == null || response.length < 3 || response[0] != ButtonlessDfuImpl.OP_CODE_RESPONSE_CODE_KEY
      || response[1] != request || (response[2] != ButtonlessDfuImpl.DFU_STATUS_SUCCESS &&
      response[2] != SecureDfuError.BUTTONLESS_ERROR_OP_CODE_NOT_SUPPORTED
        && response[2] != SecureDfuError.BUTTONLESS_ERROR_OPERATION_FAILED)) {
      hilog.error(this.DOMAIN, this.TAG, `gatStatusCode error: ${JSON.stringify(response)}`);
      throw new Error(`Invalid response received: ${JSON.stringify(response)}, ${request.toString()}`);
    }

    return response[2];
  }

  public async doPreformDfu(characteristic: ble.BLECharacteristic): Promise<void> {
    let presolve: ((value: void | PromiseLike<void>) => void) | null = null;
    this.mGatt.on("BLECharacteristicChange", (characteristic: ble.BLECharacteristic) => {
      hilog.info(this.DOMAIN, this.TAG, `doPreformDfu BLECharacteristicChange: ${JSON.stringify(characteristic)}`);

      //TODO: handle response
      /*
       * The response received from the DFU device contains:
       * +---------+--------+----------------------------------------------------+
       * | byte no | value  | description                                        |
       * +---------+--------+----------------------------------------------------+
       * | 0       | 0x20   | Response code                                      |
       * | 1       | 0x01   | The Op Code of a request that this response is for |
       * | 2       | STATUS | Status code                                        |
       * +---------+--------+----------------------------------------------------+
       */
      let caValue: Uint8Array = new Uint8Array(characteristic.characteristicValue);
      let status: number = this.getStatusCode(caValue, ButtonlessDfuImpl.OP_CODE_ENTER_BOOTLOADER_KEY);
      hilog.info(this.DOMAIN, this.TAG, `Response received (Op Code = ${caValue[1]}, Status=${status})`);
      if (status != ButtonlessDfuImpl.DFU_STATUS_SUCCESS) {
        hilog.error(this.DOMAIN, this.TAG, `Device returned error after sending Enter Bootloader: ${status}}`);
        throw new Error(`Device returned error after sending Enter Bootloader: ${status}}`);
      }
      if (this.shouldScanForBootloader()) {
        // If the device will use a different address in bootloader mode, there is no
        // reason to wait for that. The library will immediately start scanning for the
        // device advertising in bootloader mode and connect to it.
        // On some devices, e.g OnePlus 5 or Moto G60 the device was failing to connect to
        // the bootloader if not previously disconnected.
        // https://devzone.nordicsemi.com/support/278664
        this.mGatt.disconnect();
        hilog.info(this.DOMAIN, this.TAG, "Device disconnected");
      } else {
        // However, if the device is expected to use the same address, we need to wait
        // for the disconnection. Otherwise, a new connectGatt would reconnect before
        // disconnection and subsequent operations would fail.
        hilog.info(this.DOMAIN, this.TAG, "Device wait disconnected");
      }
      //TODO: 屏蔽了这个就不会进bootloader模式...
      this.finalize(false, this.shouldScanForBootloader());
      this.mGatt.off("BLECharacteristicChange");
      presolve();
    })

    return new Promise<void>((resolve) => {
      try {
        presolve = resolve;
        this.writeOpCode(characteristic, ButtonlessDfuImpl.OP_CODE_ENTER_BOOTLOADER, true);
      } catch (e) {
        hilog.error(this.DOMAIN, this.TAG, `requestMtu err: ${JSON.stringify(e)}`);
      }
    })
  }

  public async performDfu() {
    this.mProgressInfo.setProgress(DfuBaseService.PROGRESS_STARTING);
    let gatt: ble.GattClientDevice = this.mGatt;

    //TODO: The service is connected to the application, not to the bootloader
    hilog.info(this.DOMAIN, this.TAG, 'Application with buttonless update found')
    hilog.info(this.DOMAIN, this.TAG, 'Jumping to the DFU Bootloader...')

    let characteristic: ble.BLECharacteristic = this.getButtonlessDfuCharacteristic();
    let resType: number = this.getResponseType();


    await this.enableCCCD(characteristic, resType);

    try {
      // Send 'enter bootloader command'
      this.mProgressInfo.setProgress(DfuBaseService.PROGRESS_ENABLING_DFU_MODE);
      hilog.info(this.DOMAIN, this.TAG, "Sending Enter Bootloader (Op Code = 1)");

      await this.doPreformDfu(characteristic);

    } catch(e) {
      hilog.error(this.DOMAIN, this.TAG, `ButtonlessDfuImpl performDfu ${JSON.stringify(e)}`);
    }
  }
}